package support;

import client.ClientConfig;
import client.RpcClient;
import client.handle.ClientToCenterInboundHandle;
import error.InitRpcContextExeption;
import error.RemoteMethodNotFoundException;
import factory.RpcClientFactory;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.socket.nio.NioSocketChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import server.RpcClientAdapter;
import server.handle.DataInboundHandle;
import support.anno.Config;
import support.anno.RemoteClass;
import support.event.SimpleRpcEvent;
import util.AgentInvorker;
import util.AnnotaionScanner;
import util.StringUtil;
import util.WarpTool;

import java.io.IOException;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.channels.SocketChannel;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public class RpcContext {
    static Logger logger = LoggerFactory.getLogger(DataInboundHandle.class);
    private static RpcClientAdapter rpcClientToCenter = null;//维护注册中心的连接
    public static RpcClientAdapter getRpcClientToCenter(){return rpcClientToCenter;}
    public static ObjectPoolSupport objectPool = new ObjectPoolSupport(); //对象池

    public static     EventQueue eventQueue = new EventQueue();

    //为兼容spring boot starter
    static public String shost;
    static public Integer sport;

    static public void initHostAndPort(String a,int b){
        shost = a;
        sport = b;
    }

    private static Map<String,RpcInfo> scanfMap = new ConcurrentHashMap<>(); //服务提供方扫描需要暴露的服务，将其描述信息存到这里
    private static Map<String, NioSocketChannel> rpcConnectMap = new ConcurrentHashMap<>();

    private static String getConnKey(NioSocketChannel channel) throws IOException {
        InetSocketAddress remoteAddress = channel.remoteAddress();
        String key = remoteAddress.getHostName() +  ":"+ remoteAddress.getPort();
        return key;
    }

    private static void putListForMap(List<RpcInfo> list){
        list.forEach(i->{
            try {
                scanfMap.put(getInfoKeyByInit(i),i);
                logger.info("{}已被添加进RpcMap...",i);
            } catch (InitRpcContextExeption initRpcContextExeption) {
                initRpcContextExeption.printStackTrace();
            }
        });
    }

    public static ClientConfig clientConfig;

    public static int getScanMapSize(){
        return scanfMap.size();
    }

    public static String getMetaInfo(String key){
        return scanfMap.get(key)==null?null:scanfMap.get(key).getClassName();
    }

    public static void addConnect(NioSocketChannel channel) throws IOException {
        String key = getConnKey(channel);
        logger.info("新的连接已进入连接池,key:{}",key);
        rpcConnectMap.put(key,channel);
    }

    //init context时，应先找到类的接口，更具接口的全类名组装key
    public static String getInfoKeyByInit(RpcInfo info) throws InitRpcContextExeption {
        String className = info.getClassName();
        String methodName = info.getMethodName();
        Class<?> aClass = null;
        try {
            aClass = Class.forName(className);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        Class<?>[] interfaces = aClass.getInterfaces();
        if(interfaces==null || interfaces.length==0){
            throw new InitRpcContextExeption(className+ "必须有实现相应的接口才能暴露");
        }

        for (int i = 0; i <interfaces.length ; i++) {
            if(hasMethod(interfaces[i],info)){
                RpcInfo temp = new RpcInfo(interfaces[i].getName(),methodName,info.getMethodType());
                String key = getInfoKeyByCall(temp);
                return key;
               // return interfaces[i].getSimpleName() + "@" + methodName; //去掉全类名(注意)
            }
        }

        throw new InitRpcContextExeption("初始化rpc上下文失败,原因:错误的key值生成...");

//        return info.getClassName() + "@" + info.getMethodName();
    }

    private static boolean hasMethod(Class c,RpcInfo info){
        try {
            Method method = c.getMethod(info.getMethodName(), info.getMethodType());
            return true;
        } catch (NoSuchMethodException e) {
            logger.error("-----");
            return false;
        }
    }

    //客户端传过来的info,转化为Key，再根据key找对应的调用方法。
    public static String getInfoKeyByCall(RpcInfo info){
        StringBuilder builder = new StringBuilder();
        builder.append(StringUtil.getSimpleName(info.getClassName()));
        builder.append("@");
        builder.append(info.getMethodName());
        builder.append("[");

        Class[] methodType = info.getMethodType();
        if(methodType!=null && methodType.length>0){
            for (Class cc : methodType){
                builder.append(cc.getSimpleName());
                builder.append(",");
            }
        }

        builder.append("]");

        return builder.toString();
    }

    //扫描服务的需要暴露的远程接口或类，将其包装成为RpcInfo对象
    public static synchronized void initRpcInfo(String scanPath){
        List<String> scannerRes = AnnotaionScanner.scanner2(scanPath, RemoteClass.class);
        scannerRes.forEach(i->{
            try {
                Class aClass = Class.forName(i);

                //取得该class的所有需要暴露的远程方法
                List<RpcInfo> aClassAllFunc = WarpTool.classWrapToInfo(aClass);
                putListForMap(aClassAllFunc);

            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }

        });

        logger.info("RpcMap初始化完成...");
        //如果配置了注册中心，则发起节点注册
        //流程，先定位注册中心->发起连接->发送元数据。
        rpcClientToCenter = new RpcClientAdapter();
        try {
            boolean b = rpcClientToCenter.lookUpToCenter(scanPath);

            if(b) { //此时说明与注册中心连接以经建立
//                startSpyThread();
                if(rpcClientToCenter.connCenter())
                    pushMetaToCenter();
            }
        } catch (Exception e) {
            logger.error("注册中心连接失败...原因:{}",e.getMessage());
            e.printStackTrace();
            return;
        }

    }

    //重连线程，若发现断开，这里自动重连
    public static void startSpyThread(int reTime){
        new Thread(new Runnable() {
            @Override
            public void run() {
                logger.info("重连事件监听线程启动...");
                while (true){
                    try {
                        Thread.sleep(reTime);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    SimpleRpcEvent pop = eventQueue.pop();
                    if(Constans.CLIENT_FLAGE.equals(pop.getSource())){
                        logger.info("客户端开始重新连接注册中心...");
                        RpcClient one = RpcClientFactory.getOne();
                        try {
                            one.conn();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }else if(Constans.SERVER_FLAGE.equals(pop.getSource())) {
                        logger.info("服务端开始重新连接注册中心...");
                        if (rpcClientToCenter.connCenter()) {
                            logger.info("重连成功，重新推送元数据...");
                            pushMetaToCenter();
                        }
                    }else {
                        logger.error("未知事件...");
                    }
                }
            }
        }).start();
    }

    //把需要暴露的服务发送给注册中心，这里是RPC服务端的原始数据，不包含IP:Port,而且ClassName还未被格式化
    public static void pushMetaToCenter(){
        logger.info("准备将数据发送到注册中心,MetaData:{}", scanfMap);
        scanfMap.forEach((k, v) -> {
            //格式化原始数据再发送，这里会先把元数据浅拷贝一次
            rpcClientToCenter.registeInfo(v, k);
        });
    }

    //调用哪一个方法？
    public static Method findCallMethod(RpcInfo info) throws RemoteMethodNotFoundException, ClassNotFoundException, NoSuchMethodException {
        String infoKey = getInfoKeyByCall(info);
        RpcInfo metaInfo = scanfMap.get(infoKey);
        logger.info("findCallKey:{}",infoKey);
        if(metaInfo == null){
            throw new RemoteMethodNotFoundException("找不到对应的远程方法!");
        }

        Class<?> aClass = Class.forName(metaInfo.getClassName());
        Method declaredMethod = aClass.getDeclaredMethod(metaInfo.getMethodName(), metaInfo.getMethodType());
        return declaredMethod;
    }

}
